package com.lostsheep.java.design.mode.builder.inner;

import lombok.extern.slf4j.Slf4j;

import java.util.Objects;
import java.util.concurrent.*;
import java.util.stream.IntStream;

/**
 * <b><code>ThreadPoolBuilder</code></b>
 * <p/>
 * Description
 * <p/>
 * <b>Creation Time:</b> 2021/3/21.
 *
 * @author dengzhen
 * @since java-design-mode
 */
@Slf4j
public class ThreadPoolBuilder {

    /**
     * 线程池核心线程数
     */
    private Integer coreTheadSize = Runtime.getRuntime().availableProcessors();
    /**
     * 线程池最大线程数
     */
    private Integer maxPoolSize = 2 * Runtime.getRuntime().availableProcessors();
    /**
     * 空闲线程(非核心线程)能够存活的时间
     */
    private Long keepAliveTime = TimeUnit.SECONDS.toNanos(60);
    /**
     * 阻塞队列(线程池中没有开始工作的线程存放队列)
     */
    private BlockingQueue<Runnable> blockingQueue;
    /**
     * 用于自定义线程创建的工厂类
     */
    private ThreadFactory threadFactory;
    /**
     * 线程池阻塞队列满时的执行策略
     */
    private RejectedExecutionHandler executionHandler;

    public ThreadPoolBuilder coreThreadSize(Integer coreTheadSize) {
        this.coreTheadSize = coreTheadSize;
        return this;
    }

    public ThreadPoolBuilder maxPoolSize(Integer maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
        return this;
    }

    public ThreadPoolBuilder keepAliveTime(Long keepAliveTime, TimeUnit timeUnit) {
        this.keepAliveTime = timeUnit.toNanos(keepAliveTime);
        return this;
    }

    /**
     * 1. {@link SynchronousQueue}    它将任务直接提交给线程而不保持它们。当运行线程小于maxPoolSize时会创建新线程，否则触发异常策略
     * 2. {@link LinkedBlockingQueue} 默认无界队列，当运行线程大于corePoolSize时始终放入此队列，此时maxPoolSize无效。
     * 当构造LinkedBlockingQueue对象时传入参数，变为有界队列，队列满时，运行线程小于maxPoolSize时会创建新线程，否则触发异常策略
     * 3. {@link ArrayBlockingQueue}  有界队列，相对无界队列有利于控制队列大小，队列满时，运行线程小于maxPoolSize时会创建新线程，否则触发异常策略
     *
     * @param blockingQueue 1/2/3
     * @return
     */
    public ThreadPoolBuilder blockingQueue(BlockingQueue<Runnable> blockingQueue) {
        this.blockingQueue = blockingQueue;
        return this;
    }

    public ThreadPoolBuilder threadFactory(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        return this;
    }

    public ThreadPoolBuilder executionHandler(RejectedExecutionHandler executionHandler) {
        this.executionHandler = executionHandler;
        return this;
    }

    public static ThreadPoolBuilder builder() {
        return new ThreadPoolBuilder();
    }

    public ThreadPoolExecutor build() {
        return build(this);
    }

    private static ThreadPoolExecutor build(ThreadPoolBuilder builder) {
        final BlockingQueue<Runnable> blockingQueue = Objects.isNull(builder.blockingQueue)
                ? new LinkedBlockingQueue<>(builder().maxPoolSize * 2)
                : builder.blockingQueue;
        final ThreadFactory threadFactory = Objects.isNull(builder.threadFactory)
                ? Executors.defaultThreadFactory()
                : builder.threadFactory;
        final RejectedExecutionHandler executionHandler = Objects.isNull(builder.executionHandler)
                ? new ThreadPoolExecutor.AbortPolicy()
                : builder.executionHandler;
        return new ThreadPoolExecutor(
                builder.coreTheadSize, builder.maxPoolSize, builder.keepAliveTime,
                TimeUnit.SECONDS, blockingQueue, threadFactory, executionHandler);
    }


    public static void main(String[] args) {

        ThreadPoolExecutor executor = ThreadPoolBuilder.builder()
                .coreThreadSize(Runtime.getRuntime().availableProcessors())
                .maxPoolSize(2 * Runtime.getRuntime().availableProcessors())
                .keepAliveTime(60L, TimeUnit.SECONDS)
                .executionHandler(new ThreadPoolExecutor.AbortPolicy())
                .blockingQueue(new ArrayBlockingQueue<>(50))
                .threadFactory(r -> {
                    Thread thread = new Thread(r);
                    thread.setName("pool-thread" + System.currentTimeMillis());
                    thread.setDaemon(true);
                    return thread;
                })
                .build();

        IntStream.range(0, 10)
                .forEach(i -> executor.execute(
                        () -> System.out.println("thread:" + Thread.currentThread().getName() + " done in " + i))
                );
    }
}
